/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package elaborate.h2x.parser;

import elaborate.h2x.transform.DefaultTransformingModelImpl;
import elaborate.h2x.transform.TransformingModel;
import elaborate.h2x.transform.TransformingNode;
import elaborate.h2x.transform.TransformingNodeId;
import elaborate.h2x.transform.service.Services;
import java.io.File;
import java.util.Iterator;
import java.util.Map;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.Text;

/**
 *
 * @author lendle
 */
public class DefaultTransformingModelParserImpl implements TransformingModelParser{

    protected Services services=null;
    protected DefFactory defFactory=null;
    protected RelationFactory relationFactory=null;
    protected RootFactory rootFactory=null;
    protected AsRootSettingFactory asRootSettingFactory=null;

    public DefaultTransformingModelParserImpl(Services services, DefFactory defFactory, RelationFactory relationFactory, RootFactory rootFactory, AsRootSettingFactory asRootSettingFactory) {
        this.services=services;
        this.defFactory=defFactory;
        this.relationFactory=relationFactory;
        this.rootFactory=rootFactory;
        this.asRootSettingFactory=asRootSettingFactory;
    }
    
    public TransformingModel parse(Document doc) throws Exception {
        DefaultTransformingModelImpl model=new DefaultTransformingModelImpl();
        model.setDocument(doc);
        TransformingNode docRoot=createTransformTree(null, doc.getDocumentElement(), model);
        Iterator<TransformingNode> it=model.iterateAllNodes();
        while(it.hasNext()){
            TransformingNode node=it.next();
            if(node.getOriginalNode().getNodeType()==Node.ELEMENT_NODE){
                Element elementNode=(Element) node.getOriginalNode();
                if(elementNode.hasAttributeNS("h2x", "def")){
                    this.defFactory.createDef(services, model, node);
                }
                if(elementNode.hasAttributeNS("h2x", "relation")){
                    this.relationFactory.createRelation(services, model, node);
                }
                if(elementNode.hasAttributeNS("h2x", "root")){
                    //System.out.println(elementNode);
                    this.rootFactory.createRoot(services, model, node);
                }
                if(elementNode.hasAttributeNS("h2x", "as_root")){
                    //System.out.println(elementNode);
                    this.asRootSettingFactory.createAsRootSetting(services, model, node);
                }
            }
        }
        this.collectTransformingRoots(docRoot, model);
        return model;
    }
    
    /**
     * recursively traverse the XHTML tree to
     * construct a TransformingModel 
     * @param nearestEnclosingRoot
     * @param rootElement
     * @param model 
     */
    protected TransformingNode createTransformTree(TransformingNode nearestEnclosingRoot, Element rootElement, DefaultTransformingModelImpl model) throws ParserException {
        TransformingNode newNode=this.generateTransformingNode(model, nearestEnclosingRoot, rootElement);
        if (nearestEnclosingRoot != null) {
            model.setNearestEnclosingRelationship(nearestEnclosingRoot, newNode);
            newNode.setParentNode(nearestEnclosingRoot);
            nearestEnclosingRoot.getChildNodes().add(newNode);
        }
        nearestEnclosingRoot = newNode;
        Node childNode = rootElement.getFirstChild();
        while (childNode != null) {
            //iteratively process child node
            if (childNode.getNodeType() == Node.ELEMENT_NODE) {
                createTransformTree(nearestEnclosingRoot, (Element) childNode, model);
            }
            else{
                TransformingNode _newNode=this.generateTransformingNode(model, nearestEnclosingRoot, childNode);
                model.setNearestEnclosingRelationship(newNode, _newNode);
                _newNode.setParentNode(newNode);
                newNode.getChildNodes().add(_newNode);
                //System.out.println(newNode.getOriginalNode()+":"+_newNode.getOriginalNode());
            }
            
            childNode = childNode.getNextSibling();
        }
        //process attribute node
        NamedNodeMap attributes=rootElement.getAttributes();
        for(int i=0; i<attributes.getLength(); i++){
            Attr attr=(Attr) attributes.item(i);
            TransformingNode _newNode=this.generateTransformingNode(model, nearestEnclosingRoot, attr);
            model.setNearestEnclosingRelationship(newNode, _newNode);
        }
        return newNode;
    }
    /**
     * root nodes:
     * 1. included in transformation, and
     * 2. with AsRootSetting defined on it
     *
     * @param currentNode
     * @param model
     */
    protected void collectTransformingRoots(TransformingNode currentNode, DefaultTransformingModelImpl model){
        if(currentNode.isIncludedInResult()){
            if(currentNode.getAsRootSetting()!=null){
                //this node is a root node
                model.getRoots().add(currentNode);
            }
        }
        if(currentNode.getChildNodes()!=null){
            for(TransformingNode childNode : currentNode.getChildNodes()){
                this.collectTransformingRoots(childNode, model);
            }
        }
    }
    
    protected TransformingNode generateTransformingNode(DefaultTransformingModelImpl model, TransformingNode nearestEnclosingRoot, Node node) throws ParserException{
        TransformingNode newNode = new TransformingNode();
        TransformingNodeId newId = (node.getNodeType()==Node.ELEMENT_NODE && ((Element)node).hasAttribute("id"))? new TransformingNodeId(((Element)node).getAttribute("id")):new TransformingNodeId();
        newNode.setOriginalNode(node);
        newNode.setId(newId);
        model.putNode(newId, newNode);
        if (nearestEnclosingRoot != null) {
            model.setNearestEnclosingRelationship(nearestEnclosingRoot, newNode);
        }
        return newNode;
    }
    
    protected void transform(Element rootElement, DefaultTransformingModelImpl model, Map<Element, Element> transformingMap, Document newDoc){
        TransformingNode transformingNode=model.getNode(rootElement);
        Attr defAttr = rootElement.getAttributeNodeNS("h2x", "def");
        if (defAttr != null) {
            Element contextSourceParentNode=(Element) model.getNearestEnclosingNode(transformingNode).getOriginalNode();
            Node contextTargetParentnode=null;
            if(contextSourceParentNode==null){
                //root
                contextTargetParentnode=newDoc;
            }
            else{
                contextTargetParentnode=transformingMap.get(contextSourceParentNode);
                if(contextTargetParentnode==null){
                    //has not been converted yet
                    transform(contextSourceParentNode, model, transformingMap, newDoc);
                }
            }
            
            String defValue=defAttr.getValue();
            if("text()".equals(defValue)){
                Text text=newDoc.createTextNode(rootElement.getTextContent());
                contextTargetParentnode.appendChild(text);
            }
            else{
                QName qName=QNameUtil.string2QName(defValue);
                Element newElement=newDoc.createElementNS(qName.getNamespaceURI(), qName.getLocalPart());
                transformingMap.put(rootElement, newElement);
                contextTargetParentnode.appendChild(newElement);
            }
        }
        Node childNode = rootElement.getFirstChild();
        while (childNode != null) {
            if (childNode.getNodeType() == Node.ELEMENT_NODE) {
                transform((Element) childNode, model, transformingMap, newDoc);
            }
            childNode = childNode.getNextSibling();
        }
    }

    public static void main(String [] args) throws Exception{
        DocumentBuilderFactory dbf=DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(true);
        DocumentBuilder db=dbf.newDocumentBuilder();
        Document doc=db.parse(new File("example_xml.xml"));
        TransformingModelParser parser=TransformingModelParserBuilderFactory.newInstance().newTransformingModelParserBuilder().newTransformingModelParser();
        TransformingModel model=parser.parse(doc);
    }
}
